iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 5
0

今天要來講的是Databinding
直譯就是資料綁定
這也是實現MVVM架構很重要的一個library
這功能簡而言之就是讓資料跟元件綁定

舉個例子 假設現在有個Textview

<TextView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:id="@+id/tv_text"
 android:text="Hello World"
 />

那我今天要設定一個變數去改變tv_text顯示的數值時
我該怎麼做呢?
一般來說你會需要先找到這個TextView
所以要 findViewById()
找到以後再去變更它的數據

實際代碼大概會像這樣

  onResume(){
   ...
 String show = "show";
 TextView text=(TextView) findViewByid(R.id.tv_text);
 text.setText(show);
 }

那如果改用databinding呢?

layout.xml

<TextView
  android:id="@+id/tv_text"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="@{viewModel.title}"
  />

viewModel.title 就是綁定到tv_text的數據
如果要變更顯示的值
只要直接變動viewModel的數據就行了
例如

  onResume(){
   ...
   viewModel.title = "show"
   }

這樣就能變更tv_text顯示的數據了

介紹完差異後
那我們來撰寫一個實際例子
並且將它改成databinding的形式

這是今天的專案 會從master開branch開始改
solution可以看Databinding的分支

https://github.com/mars1120/jetpackMvvmDemo.git

首先開啟專案 新增一個新的activity
你可以對package 路徑點右鍵新增
如下圖
https://ithelp.ithome.com.tw/upload/images/20190920/20120279vsu3eSt1bq.png

然後選Fragment+ViewModel
https://ithelp.ithome.com.tw/upload/images/20190920/20120279rqkWh5f1E6.png

如果你是手動新增的話
記得確認
build.gradle(Module:app)

 implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
 implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'

有新增這兩項

然後因為是從master分支開出來的
而今天只會用到PreviewActivity而已
所以記得去manifest把MainActivity註解或刪掉

AndroidManifest.xml

    <application
          ...
          >
        <activity android:name=".main.PreviewActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <!--<activity android:name=".main.MainActivity">-->
            <!--<intent-filter>-->
                <!--<action android:name="android.intent.action.MAIN"/>-->

                <!--<category android:name="android.intent.category.LAUNCHER"/>-->
            <!--</intent-filter>-->
        <!--</activity>-->
    </application>

接著去修改PreviewAcitivy
串接Fragment時會傳遞一個變數
PreviewFragment.newInstance() 改為 PreviewFragment.newInstance(1)

主頁代碼

PreviewAcitivy.kt

  override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.preview_activity)
        if (savedInstanceState == null) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, PreviewFragment.newInstance(1))
                .commitNow()
        }
    }

接著修改Fragment

首先修改Instance 讓他可以接參數

    companion object {
        val ARG_PAGE = "PAGE"
        fun newInstance(param1: Int): PreviewFragment {
            val fragment = PreviewFragment()
            val args = Bundle()
            args.putInt(ARG_PAGE, param1)
            fragment.arguments = args
            return fragment
        }
    }

接著在onActivityCreated時解析數據並顯示

...

override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        //目前還沒用到
        viewModel = ViewModelProviders.of(this).get(PreviewViewModel::class.java)
        //接收數據
        val showPage = getArguments()?.getInt(ARG_PAGE) ?: 0
        //找到textview
        val tv: TextView = activity!!.findViewById(R.id.message)
        //修改顯示的數據
        tv.text = "傳遞過來的訊息是:" + showPage.toString()
        //新增click事件
        tv.setOnClickListener {
            Toast.makeText(context, "tv.text=" + tv.text, Toast.LENGTH_SHORT).show()
        }
    }

https://ithelp.ithome.com.tw/upload/images/20190920/20120279XAL8pF5jq0.png

功能為:畫面中間顯示傳遞值
點選後會跳Toast顯示資訊

接著開始把專案改為databinding的形式

先去build.gradle新增對databinding的支援

build.gradle(Module:app)

...
android {
...
dataBinding.enabled = true

}

如附圖
https://ithelp.ithome.com.tw/upload/images/20190920/20120279Py4zKm3oaE.png

接著打開preview_fragment.xml
鼠標移到layout外圈(此例為ConstraintLayout)
按alt+Enter 或點擊後左上有黃色燈號
點開後點選Convert to data binding layout
或參閱下圖
https://ithelp.ithome.com.tw/upload/images/20190920/20120279ROxk4q0ojT.png

附一下layout的變化
https://ithelp.ithome.com.tw/upload/images/20190920/20120279h6QtKapDLh.png

接著在xml宣告資料來源
preview_fragment.xml

<data>
        <variable
                name="viewModel"
                type="com.ithome11.jetpackmvvmdemo.main.ui.preview.PreviewViewModel"/>
    </data>

name可取任意名稱
type="com.ithome11.jetpackmvvmdemo.main.ui.preview.PreviewViewModel"
為Viewmodel的路徑

修改textview的資料來源

        <TextView
        android:id="@+id/message"
        android:text="@{`message:`+String.valueOf(viewModel.message)}"
              />

android:text="@{viewModel.title}"
viewModel根據name變動
title則是PreviewViewModel內的變數

關於更多databinding 字串的用法
可參閱這篇
https://stackoverflow.com/questions/38978499/how-do-i-use-databinding-to-combine-a-string-from-resources-with-a-dynamic-varia

需要注意的一點就是要小心大小寫跟變數不要打錯了
不然compile不會過
舉例來說我目前變數使用viewModel.title
但實際上viewModel內是甚麼都沒有的

如果現在就執行專案的話 會出現如下的錯誤
https://ithelp.ithome.com.tw/upload/images/20190920/20120279H2iu7PMLge.png

接著去修改viewmodel

PreviewViewModel.kt

class PreviewViewModel : ViewModel() {
    var message :Int = 10
}

然後修改Fragment 進行綁定

     private val previewViewModel: PreviewViewModel by lazy {
        return@lazy ViewModelProviders.of(this).get(PreviewViewModel::class.java)
    }
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View {
        var binding: PreviewFragmentBinding = DataBindingUtil.inflate(inflater,
        R.layout.preview_fragment, container, false)
        var rootView: View = binding.root
        // setting values to model
        binding.viewModel = previewViewModel
        return rootView
    }

PreviewFragmentBinding跟要綁定的layout.xml名稱有關
舉例來說
preview_fragment.xml = PreviewFragmentBinding
hello_world_item.xml = HelloWorldItemBinding

Fragment完整代碼

PreviewFragment.kt

class PreviewFragment : Fragment() {

    companion object {
        val ARG_PAGE = "PAGE"
        fun newInstance(param1: Int): PreviewFragment {
            val fragment = PreviewFragment()
            val args = Bundle()
            args.putInt(ARG_PAGE, param1)
            fragment.arguments = args
            return fragment
        }
    }



    private val previewViewModel: PreviewViewModel by lazy {
        return@lazy ViewModelProviders.of(this).get(PreviewViewModel::class.java)
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View {
        var binding: PreviewFragmentBinding = DataBindingUtil.inflate(inflater, R.layout.preview_fragment, container, false)
        var rootView: View = binding.root
        // setting values to model
        binding.viewModel = previewViewModel
        return rootView
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        previewViewModel.message =  getArguments()?.getInt(ARG_PAGE) ?: 0
    }
}

然後textview就會正確顯示綁定的數據了

但還有些功能還沒替換完 明天再繼續完成它

今天的代碼在此
https://github.com/mars1120/jetpackMvvmDemo/tree/Databinding


上一篇
Day4 ViewModel & LiveData
下一篇
Day6 dataBinding - 2
系列文
Android × CI/CD 如何用基本的MVVM專案實現 CI/CD 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言